/*********************************************************************************************************
* 模块名称：AT_I2C.c
* 摘    要：I2C模块
* 当前版本：1.0.0
* 作    者：jadewo
* 完成日期：2025年05月22日
* 内    容：I2C服务
* 注    意：
**********************************************************************************************************
* 取代版本：
* 作    者：
* 完成日期：
* 修改内容：
* 修改文件：
*********************************************************************************************************/

/*********************************************************************************************************
*                                              包含头文件
*********************************************************************************************************/
#include "AT_I2C.h"
#include "gd32f30x_conf.h"
#include "Systick.h"

/*********************************************************************************************************
*                                              宏定义
*********************************************************************************************************/
//读取 SDA 端口
#define READ_I2CSDA() gpio_input_bit_get(GPIOB, GPIO_PIN_9) 

//时钟线 SCL 输出高电平
#define SET_I2C_SCL() gpio_bit_set(GPIOB, GPIO_PIN_8) 
//时钟线 SCL 输出低电平
#define CLR_I2C_SCL() gpio_bit_reset(GPIOB, GPIO_PIN_8) 

//数据线 SDA 输出高电平
#define SET_I2C_SDA() gpio_bit_set(GPIOB, GPIO_PIN_9) 
//数据线 SDA 输出低电平
#define CLR_I2C_SDA() gpio_bit_reset(GPIOB, GPIO_PIN_9)

/*********************************************************************************************************
*                                              枚举结构体
*********************************************************************************************************/

/*********************************************************************************************************
*                                              内部变量定义
*********************************************************************************************************/

/*********************************************************************************************************
*                                              内部函数声明
*********************************************************************************************************/
static void ConfigI2CGPIO(void);    //配置 I2C 的 GPIO
static void SetI2CSDAAsInput(void); //将 SDA 端设置为输入
static void SetI2CSDAAsOutput(void);//将 SDA 端设置为输出
/*********************************************************************************************************
*                                              内部函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称：ConfigI2CGPIO
* 函数功能：配置I2CGPIO
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年05月22日
* 注    意：
*********************************************************************************************************/
static void ConfigI2CGPIO(void)
{
    //配置 PB8
    rcu_periph_clock_enable(RCU_GPIOB); //使能 GPIOB 的时钟
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8); //设置 GPIO 输出模式及速度
    gpio_bit_set(GPIOB, GPIO_PIN_8); //将 PB6 默认状态设置为高电平

    //配置 PB9
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); //设置 GPIO 输出模式及速度
    gpio_bit_set(GPIOB, GPIO_PIN_9); //将 PB7 默认状态设置为高电平
}

/*********************************************************************************************************
* 函数名称：SetI2CSDAAsInput
* 函数功能：设置I2C SDA 为输出
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年05月22日
* 注    意：
*********************************************************************************************************/
static void SetI2CSDAAsInput(void)
{
    gpio_init(GPIOB, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_9); //设置 GPIO 输出模式及速度
}

/*********************************************************************************************************
* 函数名称：SetI2CSDAAsOutput
* 函数功能：设置I2C SDA 为输入
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年05月22日
* 注    意：
*********************************************************************************************************/
static void SetI2CSDAAsOutput(void)
{
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9); //设置 PB9 为输出模式
    gpio_bit_set(GPIOB, GPIO_PIN_9); //状态默认设置为高电平
}
/*********************************************************************************************************
*                                              API函数实现
*********************************************************************************************************/
/*********************************************************************************************************
* 函数名称：InitAT_I2C
* 函数功能：初始化InitAT_I2C
* 输入参数：void
* 输出参数：void
* 返 回 值：void
* 创建日期：2025年05月22日
* 注    意：
*********************************************************************************************************/
void InitAT_I2C(void)
{    
    ConfigI2CGPIO(); //配置 I2C 的 GPIO
}

void GenI2CStartSig(void)
{
    SetI2CSDAAsOutput(); //将数据线 SDA 设置为输出
    SET_I2C_SDA(); //1#数据线 SDA 输出高电平
    SET_I2C_SCL(); //2#时钟线 SCL 输出高电平，2-3 之间的间隔须>4.7us 
    DelayNus(4); //延时 4us
    CLR_I2C_SDA(); //3#数据线 SDA 输出低电平，3-4 之间的间隔须>4.0us 
    DelayNus(4); //延时 4us
    CLR_I2C_SCL(); //4#时钟线 SCL 输出低电平，保持 I2C 的时钟线 SCL 为低电平，准备发送或接收数据
}

void GenI2CStopSig(void)
{
    SetI2CSDAAsOutput(); //将数据线 SDA 设置为输出
    CLR_I2C_SCL(); //1#时钟线 SCL 输出低电平
    CLR_I2C_SDA(); //2#数据线 SDA 输出低电平
    DelayNus(4); //延时 4us
    SET_I2C_SCL(); //3#时钟线 SCL 输出高电平，3-4 之间的间隔须>4.7us 
    SET_I2C_SDA(); //4#数据线 SDA 输出高电平，发送 I2C 总线结束信号，4 之后 SDA 须保持不小于 4.0us 的高电平
    DelayNus(4); //延时 4us
}

void I2CSendByte(unsigned char txd)
{ 
    unsigned char t; //循环计数器

    SetI2CSDAAsOutput(); //将数据线 SDA 设置为输出
    CLR_I2C_SCL(); //1#时钟线 SCL 输出低电平,开始数据传输

    for(t = 0; t < 8; t++) //循环 8 次，从高到低取出字节的 8 个位
    { 
        if((txd&0x80) >> 7) //2#取出字节最高位，并判断为‘0’还是‘1’，从而做出相应的操作
        {
            SET_I2C_SDA(); //数据线 SDA 输出高电平，数据位为‘1’
        }
        else
        { 
            CLR_I2C_SDA(); //数据线 SDA 输出低电平，数据位为‘0’
        }

        txd <<= 1; //左移一位，次高位移到最高位

        DelayNus(2); //延时 2us
        SET_I2C_SCL(); //3#时钟线 SCL 输出高电平
        DelayNus(2); //延时 2us
        CLR_I2C_SCL(); //4#时钟线 SCL 输出低电平
        DelayNus(2); //延时 2us 
    } 
}
unsigned char I2CReadByte(unsigned char ack)
{
    unsigned char i = 0; //i 为循环计数器
    unsigned char receive = 0; //receive 用来存放接收的数据

    SetI2CSDAAsInput(); //1#将数据线 SDA 设置为输入

    for(i = 0; i < 8; i++ ) //循环 8 次，从高到低读取字节的 8 个位
    {
        CLR_I2C_SCL(); //2#时钟线 SCL 输出低电平
        DelayNus(2); //延时 2us
        SET_I2C_SCL(); //3#时钟线 SCL 输出高电平

        receive <<= 1; //左移一位，空出新的最低位

        if(READ_I2CSDA()) //4#读取数据线 SDA 的数据位

        {
            receive++; //在 SCL 的上升沿后，数据已经稳定，因此可以取该数据，存入最低位
        }

        DelayNus(1); //延时 1us
    } 

    if (NACK == ack) //如果 ack 为 NACK
    { 
        SendI2CNAck(); //发送 NACK，非应答
    } 
    else //如果 ack 为 ACK
    {
        SendI2CAck(); //发送 ACK，应答 
    }

    return receive; //返回读取到的数据
}

unsigned char I2CWaitAck(void)
{
    unsigned char ucErrTime = 0;

    SetI2CSDAAsInput(); //将数据线 SDA 设置为输入
    SET_I2C_SCL(); //时钟线 SCL 输出高电平
    DelayNus(1); //延时 1us

    while(READ_I2CSDA()) //读回来的数据如果是高电平，即接收端没有应答
    {
        ucErrTime++; //计数器加 1

        if(ucErrTime > 250) //如果超过 250 次，则判断为接收端出现故障，因此发送结束信号
        {
            GenI2CStopSig(); //产生一个停止信号

            return 1; //返回值为 1，表示没有收到应答信号
        }
    }
    
    CLR_I2C_SCL(); //表示已收到应答信号，时钟线 SCL 输出低电平，钳住 I2C 总线

    return 0; //返回值为 0，表示接收应答成功 
}

void SendI2CAck(void)
{
    CLR_I2C_SCL(); //1#时钟线 SCL 输出低电平，开始传送应答信号
    SetI2CSDAAsOutput(); //将数据线 SDA 设置为输出
    CLR_I2C_SDA(); //2#数据线 SDA 输出低电平
    DelayNus(2); //延时 2us
    SET_I2C_SCL(); //3#时钟线 SCL 输出高电平,在 SCL 上升沿前就要把 SDA 拉低，为应答信号
    DelayNus(2); //延时 2us
    CLR_I2C_SCL(); //4#时钟线 SCL 输出低电平，钳住 I2C 总线
}

void SendI2CNAck(void)
{
    CLR_I2C_SCL(); //1#拉低时钟线，开始传送非应答信号
    SetI2CSDAAsOutput(); //将数据线 SDA 设置为输出
    SET_I2C_SDA(); //2#数据线 SDA 输出高电平
    DelayNus(2); //延时 2us
    SET_I2C_SCL (); //3#时钟线 SCL 输出高电平，在 SCL 上升沿前就要把 SDA 拉高，为非应答信号
    DelayNus(2); //延时 2us
    CLR_I2C_SCL(); //4#时钟线 SCL 输出低电平，钳住 I2C 总线
}

